﻿#include "httpget.h"
#include <QBuffer>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QTemporaryFile>
#include <QTimerEvent>
#include <QUrl>
#include "characterset.h"

QPointer<QNetworkAccessManager> network_manager;
const int min_block_size = 1024*4;
const int max_running = 16;
const int max_retry = 3;

QNetworkAccessManager &manager()
{
    static QNetworkAccessManager nam;
    return nam;
}

bool AbstractHttpGet::isTemporaryError(int err)
{
    return err == QNetworkReply::ConnectionRefusedError || err == QNetworkReply::RemoteHostClosedError
            || err == QNetworkReply::TimeoutError || err == QNetworkReply::TemporaryNetworkFailureError
            || err == QNetworkReply::NetworkSessionFailedError || err == QNetworkReply::ProxyConnectionRefusedError
            || err == QNetworkReply::ProxyConnectionClosedError || err == QNetworkReply::ProxyTimeoutError
            || err == QNetworkReply::ContentReSendError || err == QNetworkReply::ContentConflictError
            || err == QNetworkReply::InternalServerError || err == QNetworkReply::ServiceUnavailableError
            || err == QNetworkReply::UnknownNetworkError || err == QNetworkReply::UnknownProxyError
            || err == QNetworkReply::UnknownContentError || err == QNetworkReply::UnknownServerError;
}

AbstractHttpGet::AbstractHttpGet(const QString& url):mUrl(url),mRetry(0)
{
}

void AbstractHttpGet::abort()
{
    timer.stop();
    QNetworkReply *reply = mReply.data();
    if(reply)
    {
        reply->abort();
        reply->deleteLater();
    }
    emit internalCancelled();
}

void AbstractHttpGet::slotFinished()
{
    timer.stop();
    QNetworkReply *reply = mReply.data();
    QNetworkReply::NetworkError err = reply->error();
    if(err == QNetworkReply::NoError)
    {
        QUrl url = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
        if (url.isValid())
        {
            mUrl = url.toString();
            mRetry = 0;
            retry(reply);
        }
        else
        {
            readToBuffer(reply);
            reply->deleteLater();
            finishReading();
            return;
        }
    }
    else if(canRetry(err))
    {
        ++mRetry;
        retry(reply);
    }
    else
    {
        reply->deleteLater();
        handleNetError(reply->errorString());
        return;
    }
    reply->deleteLater();
}

void AbstractHttpGet::slotReadyRead()
{
    readToBuffer(mReply.data());
    timer.start(60*1000,this);
}

bool AbstractHttpGet::canRetry(int err)
{
    return mRetry <max_retry && isTemporaryError(err);
}

void AbstractHttpGet::retry(QNetworkReply *reply)
{
    if(reply)
        clearBuffer();
    reply = manager().get(QNetworkRequest(mUrl));
    createSlot(reply);
}

void AbstractHttpGet::createSlot(QNetworkReply *reply)
{
    if(reply)
    {
        reply->setParent(this);
        mReply = reply;
        connect(reply, SIGNAL(finished()), SLOT(slotFinished()));
        connect(reply, SIGNAL(readyRead()), SLOT(slotReadyRead()));
        connect(reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(internalDownloadProgress(qint64,qint64)));
    }
    else
    {
        handleOtherError();
    }
}

void AbstractHttpGet::timerEvent(QTimerEvent *e)
{
    if(e->timerId() == timer.timerId())
    {
        timer.stop();
        QNetworkReply *reply = mReply.data();
        if(reply)
        {
            reply->abort();
            if(canRetry(QNetworkReply::TimeoutError))
            {
                ++mRetry;
                retry(reply);
            }
            else
            {
                reply->deleteLater();
                handleNetError(QObject::trECSUTF8("超时"));
            }
        }
    }
    QObject::timerEvent(e);
}

HttpGet::HttpGet(const QString &url, bool createFile, const QVariant &var, const QString &templ)
    : AbstractHttpGet(url),mCreateFile(createFile),mSize(0),mTmpTemplate(templ),error_(false),mUserData(var)
{
    connect(this,SIGNAL(internalCancelled()),SLOT(deleteLater()));
    tryHead();
}

HttpGet::~HttpGet()
{
}

QByteArray HttpGet::buffer()
{
    if(!error_ && !mCreateFile && mBuffer)
        return mBuffer->buffer();
    return QByteArray();
}

void HttpGet::slotHeadFinished()
{
    QNetworkReply *reply = mReply.data();
    QNetworkReply::NetworkError err = reply->error();
    if (err == QNetworkReply::NoError)
    {
        QUrl url = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
        if (url.isValid())
        {
            mUrl = url.toString();
            mRetry = 0;
            tryHead(reply);
            return;
        }
        QVariant var = mReply.data()->header(QNetworkRequest::ContentLengthHeader);
        if(var.isValid())
        {
            bool ok = false;
            mSize = var.toLongLong(&ok);
            if(mSize>0)
            {
                //分块
                if(mSize > min_block_size)
                {
                    mRetry = 0;
                    tryFirst(reply);
                    return;
                }
            }
            else if(ok)
            {
                reply->deleteLater();
                //大小为0
                readToBuffer(reply);
                clearBuffer();
                finishReading();
                return;
            }
        }
    }
    else if(canRetry(err))
    {
        ++mRetry;
        tryHead(reply);
        return;
    }
    //尝试下载
    retry(reply);
    reply->deleteLater();
}

void HttpGet::slotFirstFinished()
{
    QNetworkReply *reply = mReply.data();
    QNetworkReply::NetworkError err = reply->error();
    if(err == QNetworkReply::NoError)
    {
        QVariant status_code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
        int code = status_code.toInt();
        if(code == 206)
        {
            readToBuffer(reply);
            //剩下内容分块
            reply->deleteLater();
            qint64 remain = mSize - min_block_size;
            int count = (remain<=min_block_size*max_running)?(remain+min_block_size-1)/min_block_size:max_running;
            qint64 average = remain/count;
            int odd = average%min_block_size;
            if(odd!=0)
            {
                average += odd;
                count = (remain+average-1)/average;
            }
            for(int i=0,start=min_block_size;i<count;i++)
            {

                if(i == count-1)
                    average = mSize - start;
                if(average>0)
                {
                    PartialHttpGet* part = new PartialHttpGet(mUrl,start,average,*this);
                    partialList.insert(part);
                    connect(part,SIGNAL(partialFinished()),SLOT(slotPartialFinished()));
                    connect(part,SIGNAL(partialNetError()),SLOT(slotPartialNetError()));
                    connect(part,SIGNAL(partialOtherError()),SLOT(slotPartialOtherError()));
                    connect(part,SIGNAL(partialReaded()),SLOT(slotPartialReaded()));
                    start += average;
                }
            }
            return;
        }
    }
    else if(canRetry(err))
    {
        ++mRetry;
        tryFirst(reply);
        return;
    }
    slotFinished();
}

void HttpGet::slotPartialFinished()
{
    if(QObject* s = sender())
    {
        partialList.remove((PartialHttpGet*)s);
        s->deleteLater();
    }
    if(partialList.isEmpty())
    {
        finishReading();
    }
}

void HttpGet::slotPartialNetError()
{
    QObject* s = sender();
    handlePartialError(s);
    QString str;
    if(s)
    {
        PartialHttpGet* p = qobject_cast<PartialHttpGet*>(s);
        if(p && p->errStrList.size()>0)
            str = p->errStrList.join(',').prepend("partial:");
    }
    handleNetError(str);
}

void HttpGet::slotPartialOtherError()
{
    QObject* s = sender();
    handlePartialError(s);
    QString str;
    if(s)
    {
        PartialHttpGet* p = qobject_cast<PartialHttpGet*>(s);
        if(p && p->errStrList.size()>0)
            str = p->errStrList.join(',').prepend("partial:");
    }
    handleOtherError(str);
}

void HttpGet::slotPartialReaded()
{
    qint64 size = mSize;
    for(QSet<PartialHttpGet*>::const_iterator it = partialList.constBegin(); it != partialList.constEnd(); it++)
    {
        PartialHttpGet* part = *it;
        if(part)
        {
            size -= (part->size-part->pos);
        }
    }
    emit downloadProgress(size,mSize,mUserData);
}

void HttpGet::internalDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
    if(mSize>min_block_size)
    {
        if(partialList.isEmpty())
            emit downloadProgress(bytesReceived,mSize,mUserData);
    }
    else
        emit downloadProgress(bytesReceived,bytesTotal,mUserData);
}

void HttpGet::readToBuffer(QNetworkReply *reply)
{
    if(!reply)
        return;
    QByteArray read=reply->readAll();
    if(read.isEmpty())
        return;
    QIODevice* device = openWriteDevice();
    if(device && device->seek(device->size()))
    {
        device->write(read.constData(),read.size());
    }
}

void HttpGet::clearBuffer()
{
    if(!mTmpFile.isNull())
    {
        if(mTmpFile.data()->isOpen())
            mTmpFile.data()->seek(0);
        mTmpFile.data()->resize(0);
    }
    if(!mBuffer.isNull())
    {
        if(mBuffer.data()->isOpen())
            mBuffer.data()->seek(0);
        mBuffer.data()->buffer().resize(0);
    }
}

void HttpGet::finishReading()
{
    if(mCreateFile)
    {
        if(mTmpFile.isNull())
        {
            handleOtherError("temp file not open");
        }
        else
        {
            mTmpFile.data()->close();
            mTmpFile.data()->setAutoRemove(false);
            QString filename = mTmpFile.data()->fileName();
            delete mTmpFile.data();
            emit finished(filename,mUserData);
            deleteLater();
        }
    }
    else if(mBuffer)
    {
        emit finished(mBuffer.data()->buffer(),mUserData);
        deleteLater();
    }
    else
        handleOtherError("buffer not ready");
}

void HttpGet::handleOtherError(const QString &errStr)
{
    error_ = true;
    if(!errStr.isEmpty())
        errStrList << errStr;
    emit error(mUserData);
    deleteLater();
}

void HttpGet::handlePartialError(QObject* sender)
{
    if(sender)
    {
        partialList.remove((PartialHttpGet*)sender);
        sender->deleteLater();
    }
    for(QSet<PartialHttpGet*>::const_iterator it = partialList.constBegin(); it != partialList.constEnd(); it++)
    {
        PartialHttpGet* part = *it;
        if(part)
        {
            part->disconnect();
            part->abort();
        }
    }
    partialList.clear();
}

void HttpGet::tryFirst(QNetworkReply *reply)
{
    if(reply)
    {
        reply->deleteLater();
        clearBuffer();
    }
    QNetworkRequest request(mUrl);
    request.setRawHeader("Range", "bytes=0-"+QByteArray::number(min_block_size-1));
    reply = manager().get(request);
    if(reply)
    {
        reply->setParent(this);
        mReply = reply;
        connect(reply, SIGNAL(finished()), SLOT(slotFirstFinished()));
        connect(reply, SIGNAL(readyRead()), SLOT(slotReadyRead()));
    }
    else
    {
        error_ = true;
        emit error(mUserData);
        deleteLater();
    }
}

void HttpGet::tryHead(QNetworkReply *reply)
{
    if(reply)
        reply->deleteLater();
    reply = manager().head(QNetworkRequest(mUrl));
    if(reply)
    {
        reply->setParent(this);
        mReply = reply;
        connect(reply,SIGNAL(finished()),SLOT(slotHeadFinished()));
    }
    else
    {
        error_ = true;
        emit error(mUserData);
        deleteLater();
    }
}

QIODevice *HttpGet::openWriteDevice()
{
    if(mCreateFile)
    {
        QTemporaryFile* tmpFile = mTmpFile.data();
        if (!tmpFile)
        {
            if(mTmpTemplate.isEmpty())
                tmpFile = new QTemporaryFile(this);
            else
                tmpFile = new QTemporaryFile(mTmpTemplate,this);
            mTmpFile = tmpFile;
        }
        if (tmpFile->isOpen() || tmpFile->open())
            return tmpFile;
        else
            tmpFile->deleteLater();
    }
    else
    {
        QBuffer* buffer = mBuffer.data();
        if(!buffer)
            mBuffer = buffer = new QBuffer(this);
        if(buffer->isOpen() || buffer->open(QIODevice::WriteOnly))
            return buffer;
        else
            buffer->deleteLater();
    }
    return 0;
}

void HttpGet::handleNetError(const QString &errStr)
{
    error_ = true;
    if(!errStr.isEmpty())
        errStrList << errStr;
    emit error(mUserData);
    deleteLater();
}

PartialHttpGet::PartialHttpGet(const QString& url, qint64 begin, qint64 range, HttpGet &parent)
    : AbstractHttpGet(url),start(begin),size(range),pos(0),control(parent)
{
    setParent(&parent);
    connect(this,SIGNAL(internalCancelled()),SLOT(deleteLater()));
    retry();
}

void PartialHttpGet::retry(QNetworkReply *reply)
{
    if(reply)
        clearBuffer();
    QNetworkRequest request(mUrl);
    request.setRawHeader("Range", "bytes="+QByteArray::number(start)+"-"+QByteArray::number(start+size-1));
    reply = manager().get(request);
    createSlot(reply);
}

void PartialHttpGet::readToBuffer(QNetworkReply *reply)
{
    QByteArray read=reply->readAll();
    if(read.isEmpty())
        return;
    QIODevice* device = control.openWriteDevice();
    if(!device)
        return;
    int len = read.size();
    qint64 begin = start + pos;
    if(device->seek(begin))
        device->write(read.constData(),len);
    pos += len;
    emit partialReaded();
}

void PartialHttpGet::clearBuffer()
{
    //do nothing
}

void PartialHttpGet::finishReading()
{
    emit partialFinished();
}

void PartialHttpGet::handleNetError(const QString &errStr)
{
    if(!errStr.isEmpty())
        errStrList << errStr;
    emit partialNetError();
}

void PartialHttpGet::handleOtherError(const QString &errStr)
{
    if(!errStr.isEmpty())
        errStrList << errStr;
    emit partialNetError();
}
